/*
 * Copyright (C) 2007-2008  Hylke van der Schaaf
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, in version 3 of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package hextd.network;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.Timer;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author hylke
 */
public class NetworkServer implements NetworkConnector {

	private Thread threadSocketListener;
	private NetworkServerSocketListener socketListener;
	private ServerSocket socketServer;
	private Set<NetworkConnection> connections;
	//
	private NetworkUser user;
	private int maxClients;
	private int nextClient = 0;
	private Timer flushTimer;

	public NetworkServer(int maxClients) {
		this.connections = Collections.synchronizedSet(new HashSet<NetworkConnection>());
		this.maxClients = maxClients;
	}

	public void startServer(NetworkUser user, int port) {
		if (flushTimer == null) {
			flushTimer = new Timer("NetworkServerFlushTimer", true);
		}
		this.user = user;
		try {
			socketServer = new ServerSocket(port);
			System.out.println("NetworkServer::startServer: Listening on port: " + port);
		} catch (IOException e) {
			System.out.println("Could not listen on port: " + port);
		}

		this.socketListener = new NetworkServerSocketListener(this, socketServer);
		this.threadSocketListener = new Thread(this.socketListener, "socketListener");
		this.threadSocketListener.start();
	}

	public void stopServer() {
		try {
			if (this.socketServer != null) {
				this.socketServer.close();
			}
		} catch (IOException ex) {
			System.err.println("NetworkServer::stopServer: IOException when closing server.");
			Logger.getLogger(NetworkServer.class.getName()).log(Level.SEVERE, null, ex);
		}
	}

	public void disconnectClients() {
		NetworkConnection[] toArray = connections.toArray(new NetworkConnection[connections.size()]);
		for (NetworkConnection c : toArray) {
			c.close();
		}
	}

	public void shutdown() {
		this.stopServer();
		this.disconnectClients();
		flushTimer.cancel();
		flushTimer = null;
	}

	public void clientConnected(Socket client) {
		//System.err.println("NetworkServer::clientConnected: new client.");
		if (connections.size() < this.maxClients) {
			try {
				//final NetworkConnection newConnection = new NetworkConnection(client, flushTimer);
				client.setKeepAlive(true);
				client.setSoTimeout(300000);
				final NetworkConnection newConnection = new NetworkConnection(client);
				newConnection.setId(++nextClient);
				LineListener l = new LineListener() {

					private NetworkConnection client = newConnection;

					@Override
					public void lineReceived(String line) {
					}

					@Override
					public void inputStopped() {
						handleInputStopped(this.client);
					}
				};
				newConnection.addLineListener(l);

				this.connections.add(newConnection);
				this.user.connectionEstablished(newConnection);
			} catch (IOException e) {
				System.err.println("IOException.");
			}
		} else {
			try {
				client.close();
			} catch (IOException ex) {
				System.err.println("NetworkServer::clientConnected: IOException when closing client.");
				Logger.getLogger(NetworkServer.class.getName()).log(Level.SEVERE, null, ex);
			}
		}
	}

	private void handleInputStopped(NetworkConnection client) {
		this.user.connectionLost(client);
		this.connections.remove(client);
	}

	@Override
	public void sendLine(String line) {
		if (line.endsWith("\n")) {
			line = line.substring(0, line.length() - 1);
		}
		for (NetworkConnection c : connections) {
			c.sendLine(line);
		}
	}
}
